# -- coding: utf-8 --
import os, time
from functools import wraps


# 异常处理装饰器
import cv2
from pykeyboard import PyKeyboard
from pymouse import PyMouse


def excepter(place):
    def execute_func(func):  # 执行函数
        @wraps(func)  # 保存原有函数或方法的函数名和文档字符串
        def execute_except(*args, **kwargs):
            try:
                result = func(*args, **kwargs)
            except Exception as e:
                # 如果出现异常则记录在日志中
                LogHandler.recorder(place, str(e))
                result = None
            return result
        return execute_except
    return execute_func


class Base:

    # 该类中的方法都是对python基础方法和库的进一步封装

    @classmethod
    def get_time(cls, format):
        """
        按提供的日期格式返回对应的日期字符串
        :param format: %Y年 %m月 %d日  %H时 %M分 %S秒，区分大小写
        :return: 日期格式字符串
        """
        return time.strftime(format, time.localtime())

    @classmethod
    def create_dir(cls, path):
        """
        判断某个路径是否存在，如果不存在则创建它
        :param path: 要检查和创建的路径
        :return: None
        """
        if not os.path.exists(path):
            os.mkdir(path)


class FileParser:   # 文件解析器

    @classmethod
    def read_json(cls, path):
        """
        读取json格式文件，可以支持//或/**/格式的注释
        :param path:json文件的路径
        :return:python数据类型，如字典或列表
        """
        import json5
        with open(path, encoding='utf8') as file:
            contents = json5.load(file)
        return contents

    @classmethod
    def read_txt(cls, path, symbol):
        """
        按行读取某个文本文件，返回每一行有效内容的列表，支持注释
        :param path: 要读取的文本文件
        :param symbol: 注释的符号
        :return: 有效内容的字符串列表
        """
        with open(path, encoding='utf8') as file:
            contents = file.readlines()
        result = []
        for item in contents:
            temp = item.strip()
            if not temp.startswith(symbol) and temp!='':
                if symbol in temp:
                    rl = temp.split(symbol)[0].strip()
                    result.append(rl)
                else:
                    result.append(temp)
        return result


class DBParser:    # 数据库解析器
    @classmethod
    def get_conn(cls, dbname):   # 先检查base.json文件中的键
        """
        通过指定的配置文件获得数据库连接对象
        :param dbname:数据库名称
        :return:数据库连接对象
        """
        import pymysql
        db_info = FileParser.read_json('../base.json')
        return pymysql.connect(**db_info[dbname])

    @classmethod
    def update(cls, sql):
        """
        传入一条DML语句，执行该语句
        :param sql: DML语句，即增删改操作
        :return: None
        """
        conn = cls.get_conn('app_db')
        cursor = conn.cursor()
        cursor.execute(sql)
        conn.commit()
        cursor.close()
        conn.close()

    @classmethod
    def query(cls, sql):
        """
        传入一条DQL语句，执行该语句并返回查询结果
        :param sql: SQL语句，即查询操作
        :return: 查询结果集，是二维元组
        """
        conn = cls.get_conn('app_db')
        cursor = conn.cursor()
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        conn.close()
        return result


class UI:
    driver = None

    @classmethod
    def get_driver(cls, browser):
        """
        通过单例模式获取浏览器驱动对象
        :param browser: 浏览器名称字符串
        :return: 浏览器驱动对象
        """
        from selenium import webdriver
        # 判断driver是否为空，为空证明是第一次打开浏览器，这时候需要实例化对象；如果不为空，则直接返回即可，不需要重新实例化
        if cls.driver is None:
            if hasattr(webdriver, browser):
                cls.driver = getattr(webdriver, browser)()
            else:
                cls.driver = webdriver.Firefox()
            cls.driver.implicitly_wait(5)
            cls.driver.maximize_window()
        return cls.driver

    @classmethod
    def get(cls, url):
        """
        打开一个url页面
        :param url: 页面地址
        :return: None
        """
        cls.driver.get(url)

    @classmethod
    def close(cls):
        """
        关闭当前页面
        :return:None
        """
        cls.driver.quit()
        cls.driver = None

    @classmethod
    def refresh(cls):
        """
        页面刷新
        :return:None
        """
        cls.driver.refresh()

    @classmethod
    def get_element(cls, locator):
        """
        根据定位方式来查找元素对象，如果找不到返回None
        :param locator:元素定位方式，格式为locator = {'by':'ID','what':'xxx'}
        :return:
        """
        by = locator['by']
        what = locator['what']
        try:

            from selenium.webdriver.common.by import By
            return cls.driver.find_element(getattr(By, by), what)
        except Exception as e:  # 没有找到，返回异常转回None
            return None

    @classmethod
    def input(cls, locator, value): # locator = ({'by':'ID','what':'xxx'},'woniu123')
        """
        向文本框元素输入内容
        :param locator: 定位方式
        :param value: 要输入的值
        :return: None
        """
        element = cls.get_element(locator)
        element.click()
        element.clear()
        element.send_keys(value)

    @classmethod
    def click(cls, locator): # locator = {'by':'ID','what':'xxx'}
        """
        点击某个元素
        :param locator:元素定位方式
        :return: None
        """
        element = cls.get_element(locator)
        element.click()

    @classmethod
    def select_option(cls,locator): # locator = {'by':'ID','what':'xxx'}
        """
        随机选择下拉数据某一项但不包括首行
        :param locator:
        :return:
        """
        element = cls.get_element(locator)
        from selenium.webdriver.support.select import Select
        size = len(Select(element).options)
        import random
        random_index = random.randint(1, size - 1)
        Select(element).select_by_index(random_index)

    @classmethod
    def exists(cls, locator):
        """
        判断某个元素是否存在，如果存在则返回True，否则返回False
        :param locator:元素定位器
        :return:boolean类型
        """
        if cls.get_element(locator) is None:
            return False
        else:
            return True

    @classmethod
    def screenshot(cls):
        """
        现场截图
        :return: None
        """
        Base.create_dir('img')
        filename = f'img/{Base.get_time("%Y%m%d%H%M%S")}.png'
        cls.driver.get_screenshot_as_file(filename)


class Asserts:
    # 该类中封装了断言方法
    @classmethod
    def assert_equal(cls, expect, actual):
        """
        断言相等
        :param expect:期望结果
        :param actual: 实际结果
        :return: None
        """
        if expect == actual:
            print('断言成功')
        else:
            print('断言失败')

    @classmethod
    def assert_contain(cls):
        pass

    @classmethod
    def assert_not_equal(cls):
        pass


class LogHandler:   # 日志处理器
    # 思路：
    # 1.在logs下创建一个文本文件，文本文件的文件名格式为：%Y/%m/%d %H:%M:%S.log
    # 2.明确要写入日志中的内容和格式：首先一个日志文件由多行记录组成，一条记录应该由：时间+地点（模块.类.方法）+内容组成；
    # 3.内容应该传参，可能是堆栈报错信息，也可能是自己想写的内容；
    # 4.以追加方式写入。a+
    @classmethod
    def get_filename(cls):
        """
        获取日志文件的文件名及其路径
        :return: 路径和文件名
        """
        Base.create_dir('logs')
        ftime = Base.get_time('%Y-%m-%d %H-%M-%S')
        return f'logs/{ftime}.log'

    @classmethod
    def get_log_record(cls, place, content):
        """
        获取日志文件内记录的格式和内容
        :param place: 地点，规范为：模块名.类名.方法名，即可知道在哪个模块下的哪个类下的哪个方法
        :param content: 记录了什么内容
        :return: 格式化后的记录
        """
        ftime = Base.get_time('%Y/%m/%d %H:%M:%S')
        return f"{ftime}---------------{place}----------------{content}\n"

    @classmethod
    def recorder(cls, place, content):
        """
        在任意位置调用该方法，记录相应的内容
        :param place:
        :param content:
        :return:
        """
        filename = cls.get_filename()
        record = cls.get_log_record(place, content)
        with open(filename, 'a+') as file:
            file.write(record)


class ImgMatchUtil:  # 会调用  图像匹配类

    mouse = PyMouse()
    keyboard = PyKeyboard()

    @classmethod
    def match_img(cls, target):
        """
        匹配图像方法，返回找到的图像的坐标，如果没找到则返回-1，-1
        :param target:
        :return:
        """
        image_path = '../image'
        template_path = os.path.join(image_path, target)
        screen_path = os.path.join(image_path, 'screen.png')
        from PIL import ImageGrab
        ImageGrab.grab().save(screen_path)

        # 读取大图
        screen = cv2.imread(screen_path)
        # 读取小图
        template = cv2.imread(template_path)
        # 调用匹配算法
        result = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED)

        min, max, min_loc, max_loc = cv2.minMaxLoc(result)

        # 计算矩形十字中心点的坐标
        x = max_loc[0] + int(template.shape[1] / 2)
        y = max_loc[1] + int(template.shape[0] / 2)

        # 如果没匹配成功，则返回的是-1，-1
        return x, y

        # 单击图片元素
    @classmethod
    def click_image(cls, target):
        x, y = cls.match_img(target)
        print(x, y)
        if x == -1 and y == -1:
            return
        cls.mouse.click(x, y)

    # 双击图片元素
    @classmethod
    def double_click_image(cls, target):
        x, y = cls.match_img(target)
        if x == -1 and y == -1:
            return
        cls.mouse.click(x, y, n=2)

    # 向一个文本框图片输入
    @classmethod
    def input_image(cls, target, value):
        x, y = cls.match_img(target)
        if x == -1 and y == -1:
            return
        cls.keyboard.type_string(value)

if __name__ == '__main__':

    # print(DBParser.get_conn('app_db'))
    # print(Base.get_time('%Y/%m/%d %H:%M:%S'))

    ImgMatchUtil.click_image('2.png')